package models

import (
	"gitee.com/zhongguo168a/go-nodex/dbx"
	"gitee.com/zhongguo168a/go-nodex/dbx/mongox"
	"gitee.com/zhongguo168a/go-nodex/nodex/filedb"
	"gitee.com/zhongguo168a/go-nodex/nodex/timer"
	"gitee.com/zhongguo168a/gocodes/datax/convertx"
	"gitee.com/zhongguo168a/gocodes/datax/listx/constraints"
	"gitee.com/zhongguo168a/gocodes/myx/errorx"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"sync"
	"time"
)

const TableTicket = "ticket"

var ticketConfig = &sync.Map{}

type TicketConfig struct {
	// 如果数据库中数量不足，每次增加的数量
	GroupCount int
}

func RegisterTicket(tag string, config *TicketConfig) {
	ticketConfig.Store(tag, config)
}

func init() {
	dbx.RegisterTable(&mongox.Table{
		Name:       TableTicket,
		Database:   NODE_DBNAME,
		PrimaryKey: "_id",
		Indexs: map[int]*mongo.IndexModel{
			0: {
				Keys: bson.D{
					{Key: "_id", Value: mongox.IndexSortOrderAscende},
				},
			},
			1: {
				Keys: bson.D{
					{Key: "Tag", Value: mongox.IndexSortOrderAscende},
					{Key: "Key", Value: mongox.IndexSortOrderAscende},
				},
			},
		},
	})

	RegisterTicket("node", &TicketConfig{
		GroupCount: 10,
	})
}

func NewTicket() (obj *Ticket) {
	obj = &Ticket{}
	obj.keepAlive = timer.NewKeeyAlive()
	obj.keepAlive.SetUpdateHandler(obj.updateTicket)
	return
}

type Ticket struct {
	Id         string `alias:"_id"`
	Tag        string
	Key        int
	UpdateTime int
	CreateTime int

	keepAlive *timer.KeeyAlive `ignore:"true"`
}

func (e *Ticket) DBTable() string {
	return TableTicket
}

func (e *Ticket) DBIdent() string {
	return e.Id
}

func (e *Ticket) DBQuery() map[string]interface{} {
	return map[string]interface{}{
		"_id": e.Id,
	}
}
func (e *Ticket) DBQueryName(id string) map[string]interface{} {
	return map[string]interface{}{
		"_id": id,
	}
}

func (e *Ticket) DBObject() interface{} {
	return e
}

func (e *Ticket) DBInit() (err error) {
	return
}

func (e *Ticket) updateTicket() (err error) {
	now := int(time.Now().Unix())
	_, updateErr := mongox.NewRequestByName(e.DBTable()).UpdateOne(bson.M{
		"_id": e.Id,
	}, bson.M{
		"$set": bson.M{
			"UpdateTime": now,
		},
	})
	if updateErr != nil {
		err = updateErr
		return err
	}
	e.UpdateTime = now
	return
}

func (e *Ticket) KeeyAlive() (err error) {
	return e.keepAlive.Run()
}

func (e *Ticket) Dispose() {
	e.keepAlive.Dispose()
}

type TicketList struct {
	constraints.List[*Ticket]
}

func (list *TicketList) DBTable() string {
	return TableNode
}

func (list *TicketList) DBQueryAll() dbx.IDBModel {
	r := NewTicket()
	list.List.Add(r)
	return r
}

func (list *TicketList) DBQuery() map[string]interface{} {
	return map[string]interface{}{}
}

// 通常用于启动节点时，执行一次的情景
func FindTicketWithLocal(tag string) (n *Ticket, err error) {
	ident, err := filedb.Get(tag)
	if err != nil {
		return
	}

	if ident != "" {
		iident_time, getErr := filedb.Get(tag + "_time")
		if getErr != nil {
			err = getErr
			return
		}
		ident_time := convertx.AnyToInt(iident_time)
		if ident_time >= 过期时间10() {
			findNode, findErr := FindTicketById(ident)
			if findErr != nil {
				err = errorx.Wrap(findErr, "find node by id")
				return
			}
			if findNode != nil && findNode.UpdateTime >= 过期时间10() { //未过期
				return findNode, nil
			}
		}
		//本地已过期，获取新的编号
	}

	{
		findTicket, findErr := FindTicketValidAndUpdate(tag)
		if findErr != nil {
			err = errorx.Wrap(findErr, "find ticket valid")
			return
		}

		if findTicket == nil {
			// 创建更多的门票
			createErr := CreateTickets(tag)
			if createErr != nil {
				err = errorx.Wrap(createErr, "create tickets")
				return
			}
		} else {
			n = findTicket
			return
		}
	}
	{
		findTicket, findErr := FindTicketValidAndUpdate(tag)
		if findErr != nil {
			err = errorx.Wrap(findErr, "find ticket valid2")
			return
		}
		if findTicket == nil {
			err = errorx.New("find ticket")
			return
		}

		n = findTicket
	}

	return

}

func CreateTickets(tag string) (err error) {
	最后, findErr := FindTicketLast(tag)
	if findErr != nil {
		err = errorx.Wrap(findErr, "find ticket last")
		return
	}
	from := 1
	if 最后 != nil {
		from = convertx.AnyToInt(最后.Key) + 1
	}

	group := 10 // 默认为10
	iconfig, has := ticketConfig.Load(tag)
	config := iconfig.(*TicketConfig)
	if has {
		group = config.GroupCount
	}

	结果 := NewTicket()
	documents := func() (x []interface{}) {
		now := int(time.Now().Unix())
		to := from + group - 1
		for i := from; i <= to; i++ {
			key := i
			x = append(x, bson.M{
				"_id":        tag + "_" + convertx.AnyToString(key),
				"Tag":        tag,
				"Key":        i,
				"UpdateTime": 0,
				"CreateTime": now,
			})
		}
		return
	}()

	_, err = mongox.NewRequestByName(结果.DBTable()).InsertMany(documents)
	if err != nil {
		return
	}

	return
}

func FindTicketById(val string) (结果 *Ticket, err error) {
	result, findErr := mongox.NewRequestByName(TableTicket).
		WithCreator(func() interface{} {
			return NewTicket()
		}).
		WithItemHandler(mongox.JSONItemHandler()).
		FindOne(map[string]interface{}{
			"_id": val,
		})
	if findErr != nil {
		if findErr != dbx.ErrNotFound {
			err = findErr
			return
		}
		return
	}
	结果 = result.(*Ticket)
	return
}

func FindTicketLast(tag string) (结果 *Ticket, err error) {
	opt := options.FindOne()
	opt.Sort = bson.D{{Key: "CreateTime", Value: -1}, {Key: "Key", Value: -1}}
	result, findErr := mongox.NewRequestByName(TableTicket).
		WithCreator(func() interface{} {
			return NewTicket()
		}).
		WithItemHandler(mongox.JSONItemHandler()).
		FindOne(map[string]interface{}{
			"Tag": tag,
		}, opt)
	if findErr != nil {
		if findErr != dbx.ErrNotFound {
			err = findErr
			return
		}
		return
	}
	结果 = result.(*Ticket)
	return
}

// 10分钟后过期
func 过期时间10() int {
	return int(time.Now().Unix()) - 10*60
}

// 60分钟后过期
func 过期时间60() int {
	return int(time.Now().Unix()) - 60*60
}

// 查找有效的门票并且更新
func FindTicketValidAndUpdate(tag string) (结果 *Ticket, err error) {

	req := mongox.NewTransactionRequest(NODE_DBNAME).
		WithCallback(func(req *mongox.TransactionRequest) (interface{}, error) {
			result, findErr := mongox.NewRequestByName(TableTicket).
				WithCreator(func() interface{} {
					return NewTicket()
				}).
				WithItemHandler(mongox.JSONItemHandler()).
				FindOne(bson.M{
					"Tag": tag,
					"UpdateTime": bson.M{
						"$lt": 过期时间60(),
					},
				})
			if findErr != nil {
				if findErr != dbx.ErrNotFound {
					err = findErr
					return nil, err
				}
				return nil, nil
			}
			ticket := result.(*Ticket)

			now := int(time.Now().Unix())
			_, updateErr := mongox.NewRequestByName(ticket.DBTable()).UpdateOne(bson.M{
				"_id": ticket.Id,
			}, bson.M{
				"$set": bson.M{
					"UpdateTime": now,
				},
			})
			if updateErr != nil {
				err = updateErr
				return nil, err
			}
			ticket.UpdateTime = now
			//
			err = filedb.Put(tag, ticket.Id)
			if err != nil {
				return nil, err
			}
			err = filedb.Put(tag+"_time", ticket.UpdateTime)
			if err != nil {
				return nil, err
			}
			return ticket, nil
		})
	result, err := req.Commit()
	if err != nil {
		return
	}

	if result[0] == nil {
		return
	}

	return result[0].(*Ticket), nil
}
